package com.msh.artascope.devcloud.service.wrapper;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.msh.artascope.devcloud.client.po.DomainConfigPo;
import com.msh.artascope.devcloud.client.po.ModuleBuildPo;
import com.msh.artascope.devcloud.client.po.ModuleIngressPo;
import com.msh.artascope.devcloud.client.po.ModuleNetworkPo;
import com.msh.artascope.devcloud.client.po.ModulePo;
import com.msh.artascope.devcloud.client.po.ModuleResourcePo;
import com.msh.artascope.devcloud.client.po.ProductPo;
import com.msh.artascope.devcloud.client.po.RegisterConfigPo;
import com.msh.artascope.devcloud.service.client.K8sClient;
import com.msh.artascope.devcloud.service.define.DevCloudDef;
import com.msh.artascope.devcloud.service.model.page.ModuleHealthTestVo;
import com.msh.artascope.devcloud.service.util.FreemarkerUtil;
import com.msh.artascope.devcloud.service.wrapper.model.PodInfo;
import com.msh.frame.client.define.StringDef;
import com.msh.frame.client.enums.YesNoEnum;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.common.util.DateUtil;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.yaml.snakeyaml.Yaml;

@Slf4j
@Component
@AllArgsConstructor
public class K8sWrapper {
  private final K8sClient k8sClient;
  private final static DateTimeFormatter ISO_INSTANT_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT.withZone(
      ZoneId.systemDefault());
  private static final Integer CODE_404 = 404;

  public void deletePods(K8sInfo k8s, ModulePo modulePo){
    List<PodInfo> pods = getPods(k8s, modulePo);
    if(!CollectionUtils.isEmpty(pods)){
      pods.stream().forEach(p->{
        try {
          k8sClient.deletePod(k8s, p.getName());
        }catch (HttpClientErrorException.NotFound e){
          log.debug("资源不存在", e);
        }
      });
    }
  }

  public List<PodInfo> getPods(K8sInfo k8s, ModulePo modulePo){
    JSONObject jsonObject = k8sClient.getPods(k8s, modulePo.getName());
    JSONArray items = jsonObject.getJSONArray("items");
    if(0 == items.size()) {
      return Collections.EMPTY_LIST;
    }
    List<PodInfo> podInfoList = new LinkedList<>();
    for(int i=0; i< items.size(); i++) {
      JSONObject podJson = items.getJSONObject(i);
      JSONObject metadata = podJson.getJSONObject("metadata");
      JSONObject status = podJson.getJSONObject("status");
      JSONObject labels = metadata.getJSONObject("labels");
      PodInfo podInfo = new PodInfo();
      String deployName = DevCloudDef.getDeployName(labels.getString("service"),
          labels.getString("version"));
      podInfo.setDeploymentName(deployName);
      podInfo.setName(metadata.getString("name"));
      podInfo.setHostIp(status.getString("hostIP"));
      podInfo.setPodIp(status.getString("podIP"));

      JSONArray conditions = status.getJSONArray("conditions");
      podInfo.setStatus(getPodStatus(conditions));

      String startTime = status.getString("startTime");
      if (!StringUtils.isEmpty(startTime)) {
        LocalDateTime localDateTime = LocalDateTime.parse(startTime,
            ISO_INSTANT_DATE_TIME_FORMATTER);
        String format = DateUtil.format(localDateTime);
        podInfo.setStartTime(format);
      }
      podInfoList.add(podInfo);
    }

    return podInfoList;
  }

  public String getPodLogs(K8sInfo k8s, String podName, Integer line) {
    try {
      return k8sClient.getLogs(k8s, podName, line);
    } catch (HttpClientErrorException.NotFound e) {
      return "";
    }
  }

  private String getPodStatus(JSONArray conditions){
    if (conditions != null) {
      for (int j = 0; j < conditions.size(); j++) {
        JSONObject condition = conditions.getJSONObject(j);
        if(null != condition){
          if ("Ready".equals(condition.getString("type"))) {
            if ("True".equals(condition.getString("status"))) {
              return "Running";
            }
          }
        }
      }
    }
    return "Unavailable";
  }

  public void createNamespace(K8sInfo k8s){
    k8sClient.createNamespace(k8s, buildNamespaceJson(k8s.getNamespace()));
  }

  public boolean hasNamespace(K8sInfo k8s){
    try {
      JSONObject namespace = k8sClient.getNamespace(k8s);
      if(null != namespace){
        return true;
      }
      log.warn("无法判断是否存在, result: {}", namespace.toJSONString());
      throw new BizException("无法判断命名空间是否存在");
    }catch (HttpClientErrorException.NotFound e){
      return false;
    }
  }

  public void deleteNamespace(K8sInfo k8s){
    try {
      k8sClient.deleteNamespace(k8s);
    }catch (HttpClientErrorException.NotFound e){
      log.debug("资源不存在", e);
    }
  }

  private String buildNamespaceJson(String namespace){
    Map<String, Object> map = new HashMap<>();
    map.put("namespace", namespace);
    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, "namespace.ftl"));
    return JSON.toJSONString(load);
  }

  public void createConfigmap(K8sInfo k8s, String logstashAddr){
    k8sClient.createConfigMap(k8s, buildConfigmapJson(k8s.getNamespace(), logstashAddr));
  }


  private String buildConfigmapJson(String namespace, String logstashAddr){
    Map<String, Object> map = new HashMap<>();
    map.put("logstashAddr", logstashAddr);
    map.put("namespace", namespace);
    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, "filebeatConfigmap.ftl"));
    return JSON.toJSONString(load);
  }

  public JSONObject getDeployment(K8sInfo k8s, String deployName) {
    try {
      return k8sClient
          .getDeployment(k8s, deployName);
    } catch (HttpClientErrorException.NotFound e) {
      log.debug("资源不存在", e);
      return null;
    }
  }


  public void deleteDeployment(K8sInfo k8s, String deployName) {
    try {
      k8sClient.deleteDeployment(k8s, deployName);
    } catch (HttpClientErrorException.NotFound e) {
      log.debug("资源不存在", e);
    }
  }

  public void deleteDeploymentByModuleName(K8sInfo k8s, String moduleName,
      String excludeDeploymentName) {
    JSONObject jsonObject = k8sClient.listDeployment(k8s, moduleName);
    JSONArray items = jsonObject.getJSONArray("items");
    if (items.isEmpty()) {
      return;
    }
    for (int i = 0; i < items.size(); i++) {
      JSONObject item = items.getJSONObject(i);
      String deploymentName = item.getJSONObject("metadata").getString("name");
      if (null != deploymentName && !deploymentName.equals(excludeDeploymentName)) {
        deleteDeployment(k8s, deploymentName);
      }
    }
  }

  public void deleteDeploymentByModuleName(K8sInfo k8s, String moduleName) {
    deleteDeploymentByModuleName(k8s, moduleName, null);
  }


  public void createDeployment(K8sInfo k8s, ProductPo productPo, ModulePo modulePo,
      ModuleBuildPo moduleBuildPo, ModuleResourcePo moduleResourcePo,
      RegisterConfigPo registerConfigPo, ModuleHealthTestVo moduleHealthTestVo,
      Map<String, String> labelMap) {
    String ftlJson = buildDeploymentJson(k8s.getNamespace(), productPo, modulePo, moduleBuildPo,
        moduleResourcePo, registerConfigPo, moduleHealthTestVo, labelMap);
    k8sClient.createDeployment(k8s, ftlJson);
  }

  private void updateDeployment(K8sInfo k8s, ProductPo productPo, ModulePo modulePo,
      ModuleBuildPo moduleBuildPo, ModuleResourcePo moduleResourcePo,
      RegisterConfigPo registerConfigPo, ModuleHealthTestVo moduleHealthTestVo,
      Map<String, String> labelMap) {
    String ftlJson = buildDeploymentJson(k8s.getNamespace(), productPo, modulePo, moduleBuildPo,
        moduleResourcePo, registerConfigPo, moduleHealthTestVo, labelMap);
    k8sClient.updateDeployment(k8s, modulePo.getName(), ftlJson);
  }

  private String buildDeploymentJson(String namespace, ProductPo productPo, ModulePo modulePo,
      ModuleBuildPo moduleBuildPo, ModuleResourcePo moduleResourcePo,
      RegisterConfigPo registerConfigPo, ModuleHealthTestVo moduleHealthTestVo,
      Map<String, String> labelMap) {
    Map<String, Object> map = new HashMap<>();
    Integer buildNum = moduleBuildPo.getBuildNum();
    map.put("productCode", productPo.getCode());
    map.put("deployName", DevCloudDef.getDeployName(modulePo.getName(), buildNum));
    map.put("moduleName", modulePo.getName());
    map.put("version", "v" + buildNum);
    map.put("labelMap", labelMap);
    map.put("namespace", namespace);
    map.put("imageName", moduleBuildPo.getImageName());
    map.put("imageAddr", registerConfigPo.getAddress());
    if (null == moduleResourcePo) {
      moduleResourcePo = new ModuleResourcePo();
      moduleResourcePo.setReplicasNum(2);
      moduleResourcePo.setJavaArgs(StringDef.EMPTY);
      moduleResourcePo.setMinMem(128);
      moduleResourcePo.setMaxMem(256);
      moduleResourcePo.setDiskSize(5);
    }
    map.put("replicasNum", moduleResourcePo.getReplicasNum());
    map.put("javaArgs", moduleResourcePo.getJavaArgs());
    map.put("minMem", moduleResourcePo.getMinMem().toString());
    map.put("maxMem", moduleResourcePo.getMaxMem().toString());
    map.put("diskSize", moduleResourcePo.getDiskSize().toString());
    map.put("live", moduleHealthTestVo.getLive());
    map.put("ready", moduleHealthTestVo.getReady());
    String deployFtlName = null;
    if("exec_jar".equals(modulePo.getType())){
      deployFtlName = "deploy.ftl";
    }else if("static".equals(modulePo.getType())){
      deployFtlName = "deploy_node.ftl";
    }else {
      throw BizException.build("未知模块类型:" + modulePo.getType());
    }
    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, deployFtlName));
    return JSON.toJSONString(load);
  }

  public void createService(K8sInfo k8s, ModulePo modulePo, ModuleNetworkPo moduleNetworkPo){
    String ftlJson = buildServiceJson(k8s.getNamespace(), modulePo, moduleNetworkPo);
    try {
      k8sClient.createService(k8s, ftlJson);
    }catch (HttpClientErrorException.Conflict e){
      //已存在
      k8sClient.deleteService(k8s, modulePo.getName());
      k8sClient.createService(k8s, ftlJson);
    }
  }

  public void deleteService(K8sInfo k8s, ModulePo modulePo){
    try {
      k8sClient.deleteService(k8s, modulePo.getName());
    }catch (HttpClientErrorException.NotFound e){
      log.debug("资源不存在", e);
    }
  }


  private String buildServiceJson(String namespace, ModulePo modulePo, ModuleNetworkPo moduleNetworkPo){
    Map<String, Object> map = new HashMap<>();
    map.put("moduleName", modulePo.getName());
    map.put("namespace", namespace);
    map.put("network", moduleNetworkPo);
    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, "service.ftl"));
    return JSON.toJSONString(load);
  }


  public void createIngress(K8sInfo k8s, ModulePo modulePo, ModuleNetworkPo moduleNetworkPo, ModuleIngressPo moduleIngressPo) {
    Map<String, Object> map = buildIngressMap(k8s.getNamespace(), modulePo,
        moduleNetworkPo, moduleIngressPo);
    /*
    String ftlJson = buildJson(map, "ingress.ftl");
    try {
      k8sClient.createIngress(k8s, ftlJson);
    }catch (HttpClientErrorException.Conflict e){
      String ingressName = ingressName(modulePo.getName(), moduleIngressPo.getDomain(), moduleIngressPo.getRequestPath(), moduleIngressPo.getAppPath());
      k8sClient.deleteIngress(k8s, ingressName);
      k8sClient.createIngress(k8s, ftlJson);
    }*/
    String moduleName = modulePo.getName();
    String gw = buildJson(map, "istio_gw.ftl");
    try {
      deleteIstioGateway(k8s, moduleName);
      k8sClient.createIstioGateway(k8s, gw);
    } catch (Exception e) {
      log.error("创建istio-gw失败", e);
      throw new BizException("创建istio-gw失败");
    }
    String vs = buildJson(map, "istio_vs.ftl");
    try {
      deleteIstioVirtualService(k8s, moduleName);
      k8sClient.createIstioVirtualService(k8s, vs);
    } catch (Exception e) {
      log.error("创建istio-vs失败", e);
      throw new BizException("创建istio-vs失败");
    }

  }


  public void deleteIngress(K8sInfo k8s, ModulePo modulePo, ModuleIngressPo moduleIngressPo){
    String name = modulePo.getName();
    try {
      deleteIstioGateway(k8s, name);
      deleteIstioVirtualService(k8s, name);
      String ingressName = ingressName(modulePo.getName(), moduleIngressPo.getDomain(),
          moduleIngressPo.getRequestPath(), moduleIngressPo.getAppPath());
      k8sClient.deleteIngress(k8s, ingressName);
    }catch (HttpClientErrorException.NotFound e){
      log.debug("资源不存在", e);
    }
  }

  private Map<String, Object> buildIngressMap(String namespace, ModulePo modulePo,
      ModuleNetworkPo moduleNetworkPo, ModuleIngressPo moduleIngressPo) {
    Map<String, Object> map = new HashMap<>();
    map.put("moduleName", modulePo.getName());
    map.put("namespace", namespace);
    map.put("servicePort", moduleNetworkPo.getServicePort());
    map.put("ingressName", ingressName(modulePo.getName(), moduleIngressPo.getDomain(),
        moduleIngressPo.getRequestPath(), moduleIngressPo.getAppPath()));
    map.put("domain", moduleIngressPo.getDomain());
    String requestPath = moduleIngressPo.getRequestPath();
    String appPath = moduleIngressPo.getAppPath();
    if (StringUtils.isEmpty(requestPath)) {
      map.put("requestPath", StringDef.SLASH);
    }else {
      map.put("requestPath", StringDef.SLASH + requestPath);
    }
    if (StringUtils.isEmpty(appPath)) {
      map.put("appPath", StringDef.SLASH);
    } else {
      map.put("appPath", StringDef.SLASH + appPath);
    }

    map.put("requestPathReplace", requestPath.replaceAll("/", "I"));
    map.put("appPathReplace", appPath.replaceAll("/", "I"));
    map.put("tslCert", moduleIngressPo.getTslCert());
    return map;
  }

  private String buildJson(Map<String, Object> map, String ftlName) {
    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, ftlName));
    return JSON.toJSONString(load);
  }

  private String ingressName(String moduleName, String domain, String requestPath, String appPath) {
    return moduleName + "-" + ingressHashCode(domain, requestPath, appPath);
  }

  private String ingressHashCode(String domain, String requestPath, String appPath) {
    int hash = (domain + requestPath + appPath).hashCode();
    hash = Math.abs(hash);
    return String.valueOf(hash);
  }

  public void replaceScale(K8sInfo k8s, String deploymentName, int replicasNum) {
    JSONObject scale = k8sClient.getScale(k8s, deploymentName);
    String resourceVersion = scale.getJSONObject("metadata").getString("resourceVersion");
    BizException.doThrowIfNull("无法获取资源版本");
    String ftlJson = buildScaleJson(k8s.getNamespace(), deploymentName, resourceVersion,
        replicasNum);
    k8sClient.updateScale(k8s, deploymentName, ftlJson);
  }

  private String buildScaleJson(String namespace, String deploymentName, String resourceVersion,
      int replicasNum) {
    Map<String, Object> map = new HashMap<>();
    map.put("deploymentName", deploymentName);
    map.put("namespace", namespace);
    map.put("resourceVersion", resourceVersion);
    map.put("replicasNum", replicasNum);

    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, "scale.ftl"));
    return JSON.toJSONString(load);
  }

  public void updateIstioGateway(K8sInfo k8s, DomainConfigPo p) {
    String domain = p.getDomain();
    String gatewayName = DevCloudDef.getGatewayName(domain);
    JSONObject istioGateway = getIstioGateway(k8s, gatewayName);
    String json = buildGatewayJson(domain, gatewayName, k8s.getNamespace(), p.getHttpsFlag(),
        p.getHttpsFlag(), p.getTslCert(), getResourceVersion(istioGateway));
    try {
      if (null == istioGateway) {
        k8sClient.createIstioGateway(k8s, json);
      } else {
        k8sClient.updateIstioGateway(k8s, gatewayName, json);
      }
    } catch (Exception e) {
      log.error("创建istio-gw失败", e);
      throw new BizException("创建istio-gw失败");
    }
  }

  public void createIstioGateway(K8sInfo k8s, DomainConfigPo p) {
    String domain = p.getDomain();
    String gatewayName = DevCloudDef.getGatewayName(domain);
    String json = buildGatewayJson(domain, gatewayName, k8s.getNamespace(), p.getHttpFlag(),
        p.getHttpsFlag(), p.getTslCert(), null);
    try {
      deleteIstioGateway(k8s, gatewayName);
      k8sClient.createIstioGateway(k8s, json);
    } catch (Exception e) {
      log.error("创建istio-gw失败", e);
      throw new BizException("创建istio-gw失败");
    }
  }

  private String buildGatewayJson(String domain, String gatewayName, String namespace,
      Integer httpFlag, Integer httpsFlag, String tslCert, String resourceVersion) {
    Map<String, Object> map = new HashMap<>();
    map.put("domain", domain);
    map.put("gatewayName", gatewayName);
    map.put("namespace", namespace);
    map.put("resourceVersion", resourceVersion);
    map.put("httpFlag", YesNoEnum.YES.getValue().equals(httpFlag));
    map.put("httpsFlag", YesNoEnum.YES.getValue().equals(httpsFlag));
    map.put("tslCert", tslCert);

    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, "istio_gw.ftl"));
    return JSON.toJSONString(load);
  }


  public void deleteIstioGateway(K8sInfo k8s, String gatewayName) {
    try {
      k8sClient.deleteIstioGateway(k8s, gatewayName);
    } catch (HttpClientErrorException.NotFound e) {
    }
  }


  private JSONObject getIstioGateway(K8sInfo k8s, String moduleName) {
    try {
      return k8sClient.getIstioGateway(k8s, moduleName);
    } catch (HttpClientErrorException.NotFound e) {
      return null;
    }
  }

  public void saveIstioVirtualService(K8sInfo k8s, String name, IstioVsInfo istioVsInfo) {
    try {
      JSONObject istioVirtualService = getIstioVirtualService(k8s, name);
      istioVsInfo.setResourceVersion(getResourceVersion(istioVirtualService));
      String json = buildVirtualServiceJson(istioVsInfo);
      if (null == istioVirtualService) {
        k8sClient.createIstioVirtualService(k8s, json);
      } else {
        k8sClient.updateIstioVirtualService(k8s, name, json);
      }
    } catch (Exception e) {
      log.error("保存或更新istio-vs失败", e);
      throw new BizException("保存或更新istio-vs失败");
    }
  }

  private String buildVirtualServiceJson(IstioVsInfo istioVsInfo) {
    return buildeJson(istioVsInfo, "istio_vs.ftl");
  }

  private String buildDestinationRule(IstioDrInfo istioDrInfo) {
    return buildeJson(istioDrInfo, "istio_dr.ftl");
  }


  private String buildeJson(Object info, String fitName) {
    Map<String, Object> map = new HashMap<>();
    map.put("info", info);

    Object load = new Yaml().load(FreemarkerUtil.replaceFtl(map, fitName));
    return JSON.toJSONString(load);
  }

  public void deleteIstioVirtualService(K8sInfo k8s, String gatewayName) {
    try {
      k8sClient.deleteIstioVirtualService(k8s, gatewayName);
    } catch (HttpClientErrorException.NotFound e) {
    }
  }

  private JSONObject getIstioVirtualService(K8sInfo k8s, String gatewayName) {
    try {
      return k8sClient.getIstioVirtualService(k8s, gatewayName);
    } catch (HttpClientErrorException.NotFound e) {
      return null;
    }
  }


  private JSONObject getIstioDestinationRule(K8sInfo k8s, String name) {
    try {
      return k8sClient.getIstioDestinationRule(k8s, name);
    } catch (HttpClientErrorException.NotFound e) {
      return null;
    }
  }

  public void saveIstioDestinationRule(K8sInfo k8s, String name, IstioDrInfo istioDrInfo) {
    try {
      JSONObject istioDestinationRule = getIstioDestinationRule(k8s, name);
      istioDrInfo.setResourceVersion(getResourceVersion(istioDestinationRule));
      String json = buildDestinationRule(istioDrInfo);
      if (null == istioDestinationRule) {
        k8sClient.createIstioDestinationRule(k8s, json);
      } else {
        k8sClient.updateIstioDestinationRule(k8s, name, json);
      }
    } catch (Exception e) {
      log.error("保存或更新istio-dr失败", e);
      throw new BizException("保存或更新istio-dr失败");
    }
  }

  public void deleteIstioDestinationRule(K8sInfo k8s, String name) {
    try {
      k8sClient.deleteIstioDestinationRule(k8s, name);
    } catch (HttpClientErrorException.NotFound e) {
    }
  }


  private String getResourceVersion(JSONObject jsonObject) {
    if (null == jsonObject) {
      return null;
    }
    jsonObject = jsonObject.getJSONObject("metadata");
    if (null == jsonObject) {
      return null;
    }
    return jsonObject.getString("resourceVersion");
  }


  @Data
  public static class K8sInfo {

    private String hostAndPort;
    private String authorization;
    private String namespace;

    public K8sInfo(String hostAndPort, String authorization, String namespace) {
      this.hostAndPort = hostAndPort;
      this.authorization = authorization;
      this.namespace = namespace;
    }
  }

  /**
   * istio-VirtualService
   */
  @Data
  @Accessors(chain = true)
  public static class IstioVsInfo {

    private String name;
    private String namespace;
    private String gatewayName;
    private String resourceVersion;
    private String domain;
    private Collection<IstioVsHttpMatchInfo> matchs;
  }

  /**
   * istio-VirtualService-match
   */
  @Data
  @Accessors(chain = true)
  public static class IstioVsHttpMatchInfo {

    private String requestPath;
    private String headerKey;
    private String headerValue;
    private String appPath;
    private List<IstioVsRoute> route;

    public boolean hasHeader() {
      return StringUtils.hasText(headerKey) && StringUtils.hasText(headerValue);
    }

    public void initWeight() {
      if (route.size() <= 1) {
        return;
      }
      int i = 0;
      int average = 100 / route.size();
      int last = 100;
      while (last - average >= average) {
        route.get(i).setWeight(average);
        last = last - average;
        i++;
      }
      route.get(i).setWeight(last);
    }
  }

  @Data
  @Accessors(chain = true)
  public static class IstioVsRoute {

    private String host;
    private Integer port;
    private String subset;
    private Integer weight;
  }


  @Data
  @Accessors(chain = true)
  public static class IstioDrInfo {

    private String name;
    private String namespace;
    private String host;
    private String resourceVersion;
    private Map<String, String> subsets;
  }

}
